#include <QAbstractTableModel>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QStringList>
#include "debug-helper.h"
#include "http-database-client.h"
#include "model-result.h"
#include "value-result.h"
#include "xml-reader.h"

HttpDatabaseClient::HttpDatabaseClient (QObject *parent) :
    AbstractDatabaseClient(parent),
    m_networkAccessManager(new QNetworkAccessManager(this)),
    m_invalidQueryError("Nieprawidłowe zapytanie lub parametry.")
{
    connect (m_networkAccessManager, &QNetworkAccessManager::finished, this, &HttpDatabaseClient::processReply);
}

void HttpDatabaseClient::setHostName (QString hostName)
{
    m_url.setHost(hostName);
}

void HttpDatabaseClient::setPort (quint16 port)
{
    m_url.setPort(port);
}

ValueResult* HttpDatabaseClient::connectToDatabase()
{
    m_url.setScheme("preconnect-http");
    QNetworkReply* reply = m_networkAccessManager->get(prepareRequest(ConnectToDatabaseRequest));
    ValueResult* result = new ValueResult(this);
    m_valueResultForReply.insert(reply, result);
    return result;
}

ValueResult *HttpDatabaseClient::disconnectFromDatabase()
{
    return 0;
}

bool HttpDatabaseClient::isConnected()
{
    return false;
}

ModelResult *HttpDatabaseClient::selectModel(const QString &queryText, const QVariantList &parameters)
{
    Q_ASSERT(!queryText.isEmpty());
    ModelResult* result = new ModelResult(this);
    QString preparedQuery = prepareQuery(queryText, parameters);
    if (preparedQuery.isEmpty())
    {
        result->setError(m_invalidQueryError);
        return result;
    }

    QNetworkReply* reply = m_networkAccessManager->post(prepareRequest(SelectModelRequest), preparedQuery.toUtf8());
    m_modelResultForReply.insert(reply, result);
    return result;
}

ValueResult* HttpDatabaseClient::selectExists (const QString &queryText, const QVariantList &parameters)
{
    Q_ASSERT(!queryText.isEmpty());
    ValueResult* result = new ValueResult(this);
    QString preparedQuery = prepareQuery(queryText, parameters);
    if (preparedQuery.isEmpty())
    {
        result->setError(m_invalidQueryError);
        return result;
    }

    QNetworkReply* reply = m_networkAccessManager->post(prepareRequest(SelectExistsRequest), preparedQuery.toUtf8());
    m_valueResultForReply.insert(reply, result);
    return result;
}

ValueResult* HttpDatabaseClient::insertData(const QString &queryText, const QVariantList &parameters)
{
    Q_ASSERT(!queryText.isEmpty());
    ValueResult* result = new ValueResult(this);
    QString preparedQuery = prepareQuery(queryText, parameters);
    if (preparedQuery.isEmpty())
    {
        result->setError(m_invalidQueryError);
        return result;
    }

    QNetworkReply* reply = m_networkAccessManager->post(prepareRequest(InsertDataRequest), preparedQuery.toUtf8());
    m_valueResultForReply.insert(reply, result);
    return result;
}

ValueResult *HttpDatabaseClient::updateData(const QString &queryText, const QVariantList &parameters)
{
    Q_ASSERT(!queryText.isEmpty());
    ValueResult* result = new ValueResult(this);
    QString preparedQuery = prepareQuery(queryText, parameters);
    if (preparedQuery.isEmpty())
    {
        result->setError(m_invalidQueryError);
        return result;
    }

    QNetworkReply* reply = m_networkAccessManager->post(prepareRequest(UpdateDataRequest), preparedQuery.toUtf8());
    m_valueResultForReply.insert(reply, result);
    return result;
}

ValueResult* HttpDatabaseClient::removeData (const QString &queryText, const QVariantList& parameters)
{
    Q_ASSERT(!queryText.isEmpty());
    ValueResult* result = new ValueResult(this);
    QString preparedQuery = prepareQuery(queryText, parameters);
    if (preparedQuery.isEmpty())
    {
        result->setError(m_invalidQueryError);
        return result;
    }

    QNetworkReply* reply = m_networkAccessManager->post(prepareRequest(RemoveDataRequest), preparedQuery.toUtf8());
    m_valueResultForReply.insert(reply, result);
    return result;
}

void HttpDatabaseClient::processReply (QNetworkReply* reply)
{
    DatabaseRequestType requestType = static_cast<DatabaseRequestType>(reply->request().attribute(QNetworkRequest::User).toInt());
    switch (requestType)
    {
    case ConnectToDatabaseRequest:
        processConnectToDatabase(reply);
        break;
    case SelectModelRequest:
        processSelectModel(reply);
        break;
    case SelectExistsRequest:
        processSelectExists(reply);
        break;
    case InsertDataRequest:
        processInsertData(reply);
        break;
    case UpdateDataRequest:
        processUpdateData(reply);
        break;
    case RemoveDataRequest:
        processRemoveData(reply);
        break;
    default:
        fatalError(requestType);
        reply->deleteLater();
    }
}

void HttpDatabaseClient::processConnectToDatabase (QNetworkReply* reply)
{
    reply->deleteLater();
    ValueResult* result = m_valueResultForReply.take(reply);
    Q_ASSERT(result);
    if (reply->error())
    {
        result->setError(reply->errorString(), reply->error());
        return;
    }

    result->setValue(true);
    m_url.setScheme("http");
}

void HttpDatabaseClient::processSelectModel (QNetworkReply *reply)
{
    reply->deleteLater();
    ModelResult* result = m_modelResultForReply.take(reply);
    if (reply->error())
    {
        result->setError(reply->errorString(), reply->error());
        return;
    }

    XmlReader xmlReader;
    QAbstractTableModel* model = xmlReader.convertToModel(reply->readAll());
    if (!model)
    {
        result->setError("Nie udało się odczytać wiadomości. " + xmlReader.errorString());
        return;
    }
    model->setParent(this);
    result->setModel(model);
}

void HttpDatabaseClient::processSelectExists (QNetworkReply *reply)
{
    reply->deleteLater();
    ValueResult* result = m_valueResultForReply.take(reply);
    if (reply->error())
    {
        result->setError(reply->errorString(), reply->error());
        return;
    }

    if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 204)
    {
        result->setValue(false);
        return;
    }

    XmlReader xmlReader;
    QAbstractTableModel* model = xmlReader.convertToModel(reply->readAll());
    if (!model)
    {
        result->setError("Nie udało się odczytać wiadomości. " + xmlReader.errorString());
        return;
    }
    result->setValue(model->rowCount() > 0);
}

void HttpDatabaseClient::processInsertData (QNetworkReply *reply)
{
    reply->deleteLater();
    ValueResult* result = m_valueResultForReply.take(reply);
    if (reply->error())
    {
        result->setError(reply->errorString(), reply->error());
        return;
    }

    XmlReader xmlReader;
    QAbstractTableModel* model = xmlReader.convertToModel(reply->readAll());
    if (!model)
    {
        result->setError("Nie udało się odczytać wiadomości. " + xmlReader.errorString());
        return;
    }
    result->setValue(model->index(0, 0).data());
}

void HttpDatabaseClient::processUpdateData (QNetworkReply *reply)
{
    reply->deleteLater();
    ValueResult* result = m_valueResultForReply.take(reply);
    if (reply->error())
    {
        result->setError(reply->errorString(), reply->error());
        return;
    }

    int code =reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    if (code == 200)
        result->setValue(true);
    else
        result->setError(QString("Nie udało się edytować danych - kod %1").arg(code), code);
}

void HttpDatabaseClient::processRemoveData (QNetworkReply *reply)
{
    reply->deleteLater();
    ValueResult* result = m_valueResultForReply.take(reply);
    if (reply->error())
    {
        result->setError(reply->errorString(), reply->error());
        return;
    }

    int code =reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    if (code == 200)
        result->setValue(true);
    else
        result->setError(QString("Nie udało się usunąć danych - kod %1").arg(code), code);
}

QString HttpDatabaseClient::prepareQuery (QString queryText, QVariantList parameters)
{
    int index = 0;
    for (int i = 0; i < parameters.size(); i++)
    {
        index = queryText.indexOf('?', index);
        if (index == -1)
            return "";
        QString parameter = parameters.at(i).toString();
        queryText.replace(index, 1, parameter);
        index += parameter.size();
    }
    return queryText;
}

QNetworkRequest HttpDatabaseClient::prepareRequest (DatabaseRequestType requestType)
{
    QNetworkRequest request(m_url);
    request.setAttribute(QNetworkRequest::User, requestType);
    request.setHeader(QNetworkRequest::ContentTypeHeader, "text/xml");
    return request;
}
